4

二叉树的中序遍历

递归版本:

var inorderTraversal = function (root, array = []) {
    if(root.left) {
        inorderTraversal(root.left,array);
    }
    array.push(root.val);
    if(root.right) {
        inorderTraversal(root.right.array);
    }
    return array;
};

循环版本:

var inorderTraversal = function (root) {
    let stack = [];
    let res = [];
    let current = root;
    while(current || stack.length > 0) {
        while(current) {
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();
        res.push(current.val);
        current = current.right;
    }
    return res;
};

二叉树的前序遍历

递归版本:

var preorderTraversal = function (root, array = []) {
    if(root) {
        array.push(root.val);
        preorderTraversal(root.left,array);
        preorderTraversal(root.right,array);
    }
};

循环版本:

var preorderTraversal = function (root) {
    let stack = [];
    let res = [];
    let current = root;
    while(current || stack.length > 0) {
        while (current) {
            res.push(current.val);
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();
        current = current.right;
    } 
    return res;
};

二叉树的后序遍历

递归版本:

var postorderTraversal = function (root, array = []) {
    if(root) {
        preorderTraversal(root.left,array);
        preorderTraversal(root.right,array);
        array.push(root.val);
    }
};

循环版本:

var postorderTraversal = function (root) {
    let stack = [];
    let res = [];
    let current = root;
    // 标记上一个访问的节点
    let last = null; 
    while(current || stack.length > 0) {
        while (current) {
            stack.push(current);
            current = current.left;
        }
        current = stack[stack.length - 1];
        // 如果没有右节点,或者右节点已经访问
        if(!current.right || current.right == last) {
            current = stack.pop();
            res.push(current.val);
            last = current;
            // 把自己置为空 继续弹栈
            current = null;
        }
        else {
            current = current.right;
        }
    } 
    return res;
};

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:二叉树前序遍历第一个点为根节点,中序遍历顺序为先左子树然后根节点最后右子树。所以先通过前序遍历找出根节点,然后将中序遍历分为左右子树两组,最后对于每个子树依次递归调用。

function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
}
//pre为前序遍历序列数组 vin为中序遍历序列数组
function reConstructBinaryTree(pre, vin){
  if(pre.length == 0 || vin.length == 0) {
    return null;
  }
  let root = new TreeNode(pre[0]);
  //根节点在中序遍历中的位置
  let index = vin.indexOf(pre[0]);
  let leftPre = pre.slice(1,index+1);//前序左子树
  let rightPre = pre.slice(index+1);//前序右子属
  let leftVin = vin.slice(0,index);//中序左子树
  let rightVin = vin.slice(index+1);//中序右子树
  //开始递归
  root.left = reConstructBinaryTree(leftPre,leftVin);
  root.right = reConstructBinaryTree(rightPre,rightVin);
  return root;
}

console.log(reConstructBinaryTree([1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]));

求二叉树的遍历

给定一棵二叉树的前序遍历和中序遍历,求其后序遍历

思路:
和上面题目的思路基本相同
前序遍历找到根结点root
找到root在中序遍历的位置 -> 左子树的长度和右子树的长度
截取左子树的中序遍历、右子树的中序遍历
截取左子树的前序遍历、右子树的前序遍历
递归拼接二叉树的后序遍历

function getPostorder(pre,vin) {
    if(pre.length == 0 || vin.length == 0) {
        return '';
    }
    let root = pre[0];
    let rootIndex = vin.indexOf(root);
    let leftVin = vin.slice(0,rootIndex);
    let rightVin = vin.slice(rootIndex+1);
    let leftPre = pre.slice(1,leftVin.length+1);
    let rightPre = pre.slice(leftVin.length+1);
    return getPostorder(leftPre,leftVin) + getPostorder(rightPre,rightVin) + pre[0];
}

let pre = 'FDXEAG';
let vin = 'XDEFAG';
console.log(getPostorder(pre,vin));

对称的二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

clipboard.png
如图,1为对称二叉树,2、3都不是。

思路:
自身和自身判断是否是镜像的,如果是,这颗树就是对称的。其实我们要写一个函数(isMirrored)去判断两个树是否是镜像的。
判断逻辑:
1、root1和root2的相同
2、root1.left 和 root2.right 是镜像的
3、root1.right 和 root2.left 是镜像的

function isSymmetrical(root) {
    return isMirrored(root,root);
}

function isMirrored(root1,root2) {
    if(!root1 && !root2) {
        return true;
    }
    if(!root1 || !root2) {
        return false;
    }
    if(root1.val != root2.val) {
        return false;
    }
    return isMirrored(root1.left,root2.right) && isMirrored(root1.right,root2.left);
}

二叉树的镜像

给定一棵二叉树,求这颗二叉树的镜像二叉树。

function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
}

function Mirror(root) {
    if(!root) {
        return null;
    }
    let node = new TreeNode();
    node.val = root.val;
    node.left = Mirror(root.right);
    node.right = Mirror(root.left);
    return node;
}

二叉搜索树的第k个节点

给定一棵二叉搜索树,请找出其中的第k小的结点。 例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

思路:
实际考察二叉搜索树的中序遍历。

递归版本:

let count = 0;
let res;

function KthNode(root,k) {
    if(!root) {
        return;
    }
    KthNode(root.left,k);
    count++;
    if(count == k ) {
        res = root.val;
        return null;
    }
    KthNode(root.right,k);
}

KthNode(tree1,3)
console.log(res);

循环版本:

function KthNode(root,k) {
    let stack = [];
    let current = root;
    let count = 0;
    let res;
    while(current || stack.length > 0) {
        while(current) {
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();
        count++;
        if(count == k) {
            res = current.val;
            break;
        }
        current = current.right;
    }
    return res;
}

console.log(KthNode(tree1,7));

二叉搜索树的后序遍历

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:
1.数组最后一位是根节点,从第0位开始,找到第一位比根节点大的元素,记录此位置middle。在此位置之前都属于左子树(此时已经断定左子树都小于根节点)
2.检查右子树是否都大于跟节点(从第middle位开始,到根节点前)如果发现有一个小于根节点,直接return false
3.递归判断左右子树是否都属于二叉搜索树。

function VerifySquenceOfBST (sequence) {
    if(sequence.length <= 0) {
        return true;
    }
    let root = sequence[sequence.length-1];
    let i = 0;
    let middle;
    // 从 0 开始寻找一个比root大的值,位置为middle
    while(sequence[i] < root) {
        i++;
    }
    middle = i;
    // 从middle开始到倒数第二个数 判断是否有小于root的数,如果有,返回flase
    while(i<sequence.length-1) {
        if(sequence[i] < root) {
            return false;
        }
        i++;
    }
    // 递归判断 左子树 和 右字数
    return VerifySquenceOfBST(sequence.slice(0,middle)) && VerifySquenceOfBST(sequence.slice(middle,sequence.length-1));
}
// 错误的
// let sequence = [8,5,3,1,4,7,6,11,9,12];

// 正确的
let sequence = [1,4,3,6,7,5,9,12,11,8];

console.log(VerifySquenceOfBST(sequence));

二叉树的最大深度

给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。

思路:
深度优先遍历 + 分治
一棵二叉树的最大深度等于左子树深度和右子树最大深度的最大值 + 1

function TreeDepth(pRoot) {
  return !pRoot ? 0 : Math.max(TreeDepth(pRoot.left), TreeDepth(pRoot.right)) + 1
}

平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。
平衡二叉树:每个子树的深度之差不超过1

function IsBalanced_Solution(pRoot) {
  return balanced(pRoot) != -1;
}

function balanced(node) {
  if (!node) {
    return 0;
  }
  const left = balanced(node.left);
  const right = balanced(node.right);
  if (left == -1 || right == -1 || Math.abs(left - right) > 1) {
    return -1;
  }
  return Math.max(left, right) + 1;
}

不分行从上到下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:
在打印第一行时,将左孩子节点和右孩子节点存入一个队列里
队列元素出队列打印,同时分别将左孩子节点和右孩子节点存入队列
这样打印二叉树的顺序就是没行从左到右打印

function PrintFromTopToBottom(root) {
    let queue = [];
    let res = [];
    if(root) {
        queue.push(root);
        while(queue.length > 0) {
            let current = queue.shift();
            res.push(current.val);
            if(current.left) {
                queue.push(current.left);
            }
            if(current.right) {
                queue.push(current.right);
            }
        }
    }  
    return res;
}

把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路:
使用两层循环,第一层循环控制行,第二层循环控制列。第二层循环遍历列的时候会把下一行入队列。

function Print(root) {
    let queue = [];
    let res = [];
    let temp = [];
    queue.push(root);
    while(queue.length > 0) {
        let i = 0;
        let count = queue.length;
        while(i < count) {
            let current = queue.shift();
            temp.push(current.val);
            if(current.left) {
                queue.push(current.left);
            }
            if(current.right) {
                queue.push(current.right);
            }
            i++;
        }
        res.push(temp);
        temp = [];
    }
    return res;
}

按之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

思路:
在上一题中稍做改动就好。
分奇偶行。
如果是奇数行:按照上题的方法。
如果是偶数行:从队尾出队,先把右节点从队头入队,再把左节点从队头入队。

function Print(root) {
    let queue = [];
    let res = [];
    let temp = [];
    queue.push(root);
    let j = 1;
    while(queue.length > 0) {
        let i = 0;
        let count = queue.length;
        // 偶数行
        if(j%2 == 0) {
            while(i < count) {
                let current = queue.pop();
                temp.push(current.val);
                if(current.right) {
                    queue.unshift(current.right);
                }
                if(current.left) {
                    queue.unshift(current.left);
                }
                i++;
            }
        }
        else {
            while(i < count) {
                let current = queue.shift();
                temp.push(current.val);
                if(current.left) {
                    queue.push(current.left);
                }
                if(current.right) {
                    queue.push(current.right);
                }
                i++;
            }
        }
        res.push(temp);
        temp = [];
        j++;
    }
    return res;
}

二叉树的下一个节点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路:根据中序遍历的特点,要找到一个节点的下一个节点无非就是三种情况:1、有右子树,这时只需要把其右孩子作为下一个遍历的(并不是要找的)节点,然后沿着该节点的左子树(如果有的话)出发,直到遇到叶子节点,那么该叶子节点就是其下一个要找的节点;2、没有右子树,则判断该节点是否是其父节点的左孩子,如果是则其下一个要找的节点是其父节点;3、如果不是其父节点的左孩子,则把其父节点作为下一个遍历的节点,向上回溯,直到找到父节点没有父节点并且父节点是父节点的父节点的左孩子为止。综合这三种情况就可以找到二叉树中任意一个节点的下一个节点。

function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
    this.parent = null;
}
function getNext(node) {
  let curNode = null;
  //第一、判断是否有右孩子
  if(node.right != null) {
    curNode = node.right;
    while(curNode.left !=null) {
      curNode = curNode.left;
    }
    return curNode;
  }
  //第二、判断是否是其父节点的左孩子
  if(node.parent == null) {
    return null;
  }
  if(node.parent.left == node) {
    return node.parent;
  }
  //第三、向上找其父节点,直到父节点是其父节点的父节点的左孩子
  curNode = node.parent;
  while(curNode.parent != null) {
    if(curNode == curNode.parent.left) {
      return curNode.parent;
    }
    curNode = curNode.parent;
  }
  return null;
}

//构建二叉树
let parentNode = createTree(
  ['a','b','d','e','h','i','c','f','g'],
  ['d','b','h','e','i','a','f','c','g']);

let i = parentNode.left.right.right;//i节点
let b = parentNode.left;//b节点
let f = parentNode.right.left;//f节点
console.log(getNext(i).val);//a
console.log(getNext(b).val);//h
console.log(getNext(f).val);//c

//根据前序遍历和中序遍历构建一颗二叉树
function createTree(pre,vin){
  function TreeNode(x) {
      this.val = x;
      this.left = null;
      this.right = null;
      this.parent = null;
  }
  //pre为前序遍历序列数组 vin为中序遍历序列数组
  function reConstructBinaryTree(pre, vin,parent){
    if(pre.length == 0 || vin.length == 0) {
      return null;
    }
    let root = new TreeNode(pre[0]);
    //根节点在中序遍历中的位置
    let index = vin.indexOf(pre[0]);
    let leftPre = pre.slice(1,index+1);//前序左子树
    let rightPre = pre.slice(index+1);//前序右子属
    let leftVin = vin.slice(0,index);//中序左子树
    let rightVin = vin.slice(index+1);//中序右子树
    //开始递归
    root.parent = parent;
    root.left = reConstructBinaryTree(leftPre,leftVin,root);
    root.right = reConstructBinaryTree(rightPre,rightVin,root);
    return root;
  }

  return reConstructBinaryTree(pre,vin,null);
}

鸡蛋炒番茄
1.1k 声望1.3k 粉丝

hello world